<?php

/***

LZRoster: A roster for World of Warcraft.
Copyright (C) 2007  Emmanuel Cron

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

***/

require_once dirname(__FILE__)."/Character.class.php";
require_once dirname(__FILE__)."/Config.class.php";
require_once dirname(__FILE__)."/Sql.class.php";
require_once dirname(__FILE__)."/TradeskillContainer.class.php";

/**
 * Manages the characters of the roster.
 * 
 * @author Emmanuel Cron
 *
 */
class CharacterManager {
	/** Single instance of this class. */
	static private $instance;
	
	/**
	 * Gets an instance of this class or
	 * creates a new one if none exists.
	 * 
	 * @return CharacterManager an instance
	 * 		of this class
	 */
	static function GetInstance() {
		if ( empty(self::$instance) ) {
			self::$instance = new CharacterManager();
		}
		
		return self::$instance;
	}
	
	/**
	 * Deletes the given character from the roster.
	 * This function deletes the character, the
	 * associated dungeons acess, professions,
	 * and specializations.
	 * 
	 * @param $name the name of the character to delete
	 * @return boolean true if character has been
	 * 		deleted, false otherwise
	 */
	function DeleteCharacter($name) {
		return $this->DeleteCharacters(array($name));
	}
	
	/**
	 * Deletes the given list of characters from the roster.
	 * The given list should be an array with the exact
	 * names of the characters to delete.
	 * 
	 * This function deletes the characters, their
	 * associated dungeon acess, professions,
	 * and specializations.
	 * 
	 * @param $names an array of the names of the characters
	 * 		to delete
	 * @return boolean true if characters have been
	 * 		deleted, false otherwise
	 */
	function DeleteCharacters($names) {
		// Cannot delete if list is null
		if ($names == null) {
			return false;
		}
		
		// Cannot delete if list is not a list
		if (!is_array($names)) {
			return false;
		}
		
		// Deletion automatically successful
		// if no character to delete
		if (count($names) < 1) {
			return true;
		}
		
		// Delete characters
		if ( Sql::GetInstance()->Query("
			DELETE FROM
				`cp_characters`
			WHERE
				`Name` IN ('".implode("','", $names)."')
		") != true) {
			return false;
		}
		
		// Delete dungeon access
		if ( Sql::GetInstance()->Query("
			DELETE FROM
				`cp_dungeons`
			WHERE
				`CharacterName` IN ('".implode("','", $names)."')
		") != true) {
			return false;
		}
		
		// Delete professions
		if ( Sql::GetInstance()->Query("
			DELETE FROM
				`cp_professions`
			WHERE
				`CharacterName` IN ('".implode("','", $names)."')
		") != true) {
			return false;
		}
		
		// Delete specializations
		if ( Sql::GetInstance()->Query("
			DELETE FROM
				`cp_specializations`
			WHERE
				`CharacterName` IN ('".implode("','", $names)."')
		") != true) {
			return false;
		}
		
		return true;
	}
	
	/**
	 * Deletes the given tradeskill from the list
	 * of the given character's known tradeskills.
	 * 
	 * @param $name name of the character from
	 * 		which delete the tradeskill
	 * @param $tradeskill_id the id of the
	 * 		tradeskill to delete, such as 'Bandage',
	 * 		'Other', ...
	 * @return boolean true if deletion was
	 * 		successful, false otherwise
	 */
	function DeleteCharacterTradeskills($name, $tradeskill_id) {
		$tradeskill = TradeskillContainer::GetInstance()->GetTradeskillById($tradeskill_id);
		
		if ( Sql::GetInstance()->Query("
			DELETE FROM
				`cp_professions`
			WHERE
				`CharacterName`='".$name."'
			AND
				`ProfessionName` IN ('".implode("','", $tradeskill->GetLocales())."')
		") != true) {
			return false;
		}
		
		return true;
	}
	
	/**
	 * Deletes a character tradeskill specialization.
	 * 
	 * @param $name name of the character from which
	 * 		delete the specialization
	 * @param $specialization_id the id of the
	 * 		specialization to delete, such as
	 * 		'alchemy_potion_master', ...
	 * @return boolean true if deletion was
	 * 		successful, false otherwise
	 */
	function DeleteCharacterTradeskillSpecialization($name, $specialization_id)
	{
		if ( Sql::GetInstance()->Query("
			DELETE FROM
				`cp_specializations`
			WHERE
				`CharacterName`='".$name."'
			AND
				`Specialization`='".$specialization_id."'
		") != true) {
			return false;
		}
		
		return true;
	}
	
	/**
	 * Returns the list of characters belonging to the
	 * given account id.
	 * 
	 * @param $id the id of the account to retrieve the
	 * 		characters
	 * @return array an array of Character objects
	 */
	function GetCharactersAndProfessionsByAccountId($id)
	{		
		$characters_array = array();
		
		// Getting characters with professions & specializations
		Sql::GetInstance()->Query("
			SELECT DISTINCT
				`cp_characters`.*,
				GROUP_CONCAT(DISTINCT `cp_specializations`.`Specialization`) AS `Specializations`,
				GROUP_CONCAT(DISTINCT `cp_professions`.`ProfessionName`) AS `Professions`
			FROM
				`cp_characters`
			LEFT JOIN
				`cp_professions`
			ON
				`cp_professions`.`CharacterName`=`cp_characters`.`Name`
			LEFT JOIN
				`cp_specializations`
			ON
				`cp_specializations`.`CharacterName`=`cp_characters`.`Name`
			WHERE
				`cp_characters`.`character_account_id`='".$id."'
			GROUP BY
				`cp_characters`.`character_id`
			ORDER BY
				`cp_characters`.`Name`
		");
		
		while ( $v = Sql::GetInstance()->IterateResults() ) {
			$characters_array[$v['Name']] = new Character($v);
		}
		
		return $characters_array;
	}
	
	/**
	 * Returns a list of Character objects not linked to
	 * any accounts (and thus that should be deleted).
	 * 
	 * @return an array with 0 or more Character objects
	 */
	function GetCharactersWithInvalidAccount() {
		$characters_array = array();
		
		// Getting characters with professions & specializations
		Sql::GetInstance()->Query("
			SELECT
				`cp_characters`.*
			FROM
				`cp_characters`
			LEFT JOIN
				`cp_accounts`
			ON
				`cp_characters`.`character_account_id`=`cp_accounts`.`account_id`
			WHERE
				`cp_accounts`.`account_id` IS NULL
			ORDER BY
				`cp_characters`.`Name`
		");
		
		while ( $v = Sql::GetInstance()->IterateResults() ) {
			$characters_array[$v['Name']] = new Character($v);
		}
		
		return $characters_array;
	}
	
	/**
	 * Returns all the characters of the roster, sorted
	 * with the given method. All professions and
	 * specializations known by the characters are also
	 * returned.
	 * 
	 * The returned array may be a two dimensional array.
	 * If the sort method is divided, the array will have
	 * the following format:
	 * 
	 * Array {
	 * 	[subsection1] = { CharacterA, CharacterB, ... }
	 * 	[subsection2] = { CharacterC, CharacterA, ... }
	 * }
	 * 
	 * A character may be present in several subsections,
	 * depending on the chosen sort method.
	 * 
	 * If the sort method is not divided, then the array
	 * will have the following format:
	 * 
	 * Array {
	 * 	CharacterA,
	 *  CharacterB,
	 *  ...
	 * }
	 * 
	 * @param $character_sort_method a CharacterSortMethod
	 * 		object
	 * @return array an array containing a list of
	 * 		Character objects
	 */
	function GetAllCharacters( $character_sort_method )
	{
		$characters_array = array();
		
		// Getting all characters with their known professions
		// and specializations
		Sql::GetInstance()->Query("
			SELECT
				`cp_characters`.*,
				`cp_accounts`.`account_nickname`,
				GROUP_CONCAT(DISTINCT `cp_specializations`.`Specialization`) AS `Specializations`,
				GROUP_CONCAT(DISTINCT `cp_professions`.`ProfessionName`) AS `Professions`
			FROM
				`cp_characters`
			LEFT JOIN
				`cp_accounts`
			ON
				`cp_accounts`.`account_id` = `cp_characters`.`character_account_id`
			LEFT JOIN
				`cp_professions`
			ON
				`cp_professions`.`CharacterName`=`cp_characters`.`Name`
			LEFT JOIN
				`cp_specializations`
			ON
				`cp_specializations`.`CharacterName` = `cp_characters`.`Name`
			".$character_sort_method->GetGroupBySqlQuery()."
		");
		
		while ( $v = Sql::GetInstance()->IterateResults() ) {
			if ( $character_sort_method->IsDivided() ) {
				// If sort method is divided, get the subsection(s) in
				// which the character should appear (belongs to)
				
				$subsections = $character_sort_method->GetSubSectionsName($v);
				
				if (is_array($subsections)) {
					// If there is several subsections, add the character
					// to each of these subsections
					
					$character = new Character($v);
					
					foreach ($subsections as $subsection) {
						$characters_array = $this->AddSubsectionAndCharacter($characters_array, $subsection, $character);
					}
				} else {
					// If there is only one subsection, add the character to the subsection
					
					$characters_array = $this->AddSubsectionAndCharacter($characters_array, $subsections, new Character($v));
				}
			} else {
				// If sort method is not divided, then simply
				// add the character to the array
				
				array_push($characters_array, new Character($v));
			}
		}
		
		return $characters_array;
	}
	
	/**
	 * Adds the given character to the given
	 * subsection in the given array. If
	 * subsection does not existe, it is
	 * created.
	 * 
	 * @param $array the array in which add
	 * 		the character
	 * @param $subsection the subsection
	 * 		in which add the character
	 * @param $character the character to add
	 * @return array the given array in which
	 * 		the new character has been added
	 */
	private function AddSubsectionAndCharacter($array, $subsection, $character)
	{
		if ( !array_key_exists($subsection, $array)) {
			$array[$subsection] = array();
		}
		
		array_push($array[$subsection], $character);
		
		return $array;
	}
	
	/**
	 * Returns all the characters of the roster with
	 * their associated dungeons access.
	 * 
	 * @return array an array containing a list
	 * 		of characters with their dungeons access
	 */
	function GetAllCharactersWithDungeonsAccess()
	{
		$characters_array = array();
		
		Sql::GetInstance()->Query("
			SELECT
				`cp_characters`.*,
				`cp_dungeons`.`Dungeon`
			FROM
				`cp_characters`
			LEFT JOIN
				`cp_dungeons`
			ON
				`cp_dungeons`.`CharacterName`=`cp_characters`.`Name`
			ORDER BY
				`cp_characters`.`Name`
		");
		
		while ( $v = Sql::GetInstance()->IterateResults() ) {
			if ( !array_key_exists($v['Name'], $characters_array) ) {
				// If characterdoes not exist, create it
				$characters_array[$v['Name']] = new Character($v);
			} else {
				// If character already exists, simply add the access
				// to it
				$characters_array[$v['Name']]->AddDungeon($v['Dungeon']);
			}
		}
		
		return $characters_array;
	}
	
	/**
	 * Returns the Character object corresponding
	 * to the given id in the database. If not
	 * character is found, null is returned.
	 * 
	 * @param $id the id of the character to get
	 * @return a Character object or null if not
	 * 		found
	 */
	function GetCharacterById($id) {
		Sql::GetInstance()->Query("
			SELECT
				`cp_characters`.*
			FROM
				`cp_characters`
			WHERE
				`cp_characters`.`character_id` = '".$id."'
		");
		
		while ( $v = Sql::GetInstance()->IterateResults() ) {
			return new Character($v);
		}
		
		return null;
	}
	
	/**
	 * Returns the list of outdated characters,
	 * defined by characters with an age greater
	 * than the grace period defined in the roster
	 * config.
	 * 
	 * @return an array with the names of the
	 * 		outdated characters
	 */
	function GetOutdatedCharactersNames() {
		Sql::GetInstance()->Query("
			SELECT
				`cp_characters`.`Name`
			FROM
				`cp_characters`
			WHERE
				`cp_characters`.`timestampUpdate` < '".(time() - Config::GetInstance()->GetGracePeriod()->GetPeriod())."'
		");
		
		$outdatedCharacters = array();
		
		while ( $v = Sql::GetInstance()->IterateResults() ) {
			array_push($outdatedCharacters, $v['Name']);
		}
		
		return $outdatedCharacters;
	}
}

?>